package testr5rs

import (
	"showen/eval"
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestSyntaxRulesNot(t *testing.T) {
	input := `(define-syntax not
				(syntax-rules ()
				  ((_ x) (if x #f #t))))
			  (not #t)
			  (not #f)`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// The last expression (not #f) should evaluate to #t
	if result.Inspect() != "#t" {
		t.Fatalf("expected #t, got %s", result.Inspect())
	}
}

func TestSyntaxRulesUnlessEllipsis(t *testing.T) {
	input := `(define-syntax unless
				(syntax-rules ()
				  ((_ test body ...)
				   (if (not test) (begin body ...)))))
			  (unless #f 42)
			  (unless #t 42)`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	// Evaluate the program and inspect the last result (unless #t 42)
	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// The second unless should evaluate to the null-object (no value)
	if result != obj.NULL_OBJECT {
		t.Fatalf("expected null object for (unless #t 42), got %s", result.Inspect())
	}
}

func TestMacroHygieneSwap(t *testing.T) {
	input := `(define-syntax swap!
				(syntax-rules ()
				  ((_ a b)
				   (let ((tmp a)) (set! a b) (set! b tmp)))))
			   (let ((tmp 5) (a 1) (b 2)) (swap! a b) tmp)`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// The outer tmp should remain 5 if the macro's tmp is hygienic
	if result.Inspect() != "5" {
		t.Fatalf("expected outer tmp to be 5, got %s", result.Inspect())
	}
}

func TestNestedEllipsisGroupIdentity(t *testing.T) {
	input := `(define-syntax group
				(syntax-rules ()
				  ((_ (x ...) ...)
				   (list (list x ...) ...))))
			  (group (1 2) (3 4 5))`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect ((1 2) (3 4 5))
	if result.Inspect() != "((1 2) (3 4 5))" {
		t.Fatalf("expected ((1 2) (3 4 5)), got %s", result.Inspect())
	}
}

func TestNestedEllipsisMakePairs(t *testing.T) {
	// Simpler variant: capture the inner list as a whole (vs) to avoid inner ellipsis
	input := `(define-syntax make-pairs
								(syntax-rules ()
									((_ (k vs) ...)
									 (list (list k vs) ...))))
							(make-pairs (1 (2 3)) (4 (5)) (6 ()))`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect ((1 (2 3)) (4 (5)) (6 ()))
	if result.Inspect() != "((1 (2 3)) (4 (5)) (6 ()))" {
		t.Fatalf("expected ((1 (2 3)) (4 (5)) (6 ())), got %s", result.Inspect())
	}
}

func TestNestedEllipsisEmptyInner(t *testing.T) {
	input := `(define-syntax group
				(syntax-rules ()
				  ((_ (x ...) ...)
				   (list (list x ...) ...))))
			  (group () (3 4 5))`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect (() (3 4 5))
	if result.Inspect() != "(() (3 4 5))" {
		t.Fatalf("expected (() (3 4 5)), got %s", result.Inspect())
	}
}

func TestNestedEllipsisMixedLengths(t *testing.T) {
	input := `(define-syntax group
				(syntax-rules ()
				  ((_ (x ...) ...)
				   (list (list x ...) ...))))
			  (group (1) (2 3) (4 5 6))`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect ((1) (2 3) (4 5 6))
	if result.Inspect() != "((1) (2 3) (4 5 6))" {
		t.Fatalf("expected ((1) (2 3) (4 5 6)), got %s", result.Inspect())
	}
}

func TestNestedEllipsisSingletons(t *testing.T) {
	input := `(define-syntax group
				(syntax-rules ()
				  ((_ (x ...) ...)
				   (list (list x ...) ...))))
			  (group (42) (7) ())`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect ((42) (7) ())
	if result.Inspect() != "((42) (7) ())" {
		t.Fatalf("expected ((42) (7) ()), got %s", result.Inspect())
	}
}

func TestNestedEllipsisAllEmpty(t *testing.T) {
	input := `(define-syntax group
				(syntax-rules ()
				  ((_ (x ...) ...)
				   (list (list x ...) ...))))
			  (group () ())`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// Expect (() ())
	if result.Inspect() != "(() ())" {
		t.Fatalf("expected (() ()), got %s", result.Inspect())
	}
}

func TestNestedEllipsisHygieneRepeatedSwap(t *testing.T) {
	input := `(define-syntax swap!
				(syntax-rules ()
				  ((_ a b)
				   (let ((tmp a)) (set! a b) (set! b tmp)))))
			  (define-syntax swaps
				(syntax-rules ()
				  ((_ (a b) ...)
				   (begin (swap! a b) ...))))
			  (let ((tmp 999) (a 1) (b 2) (c 3) (d 4))
				(swaps (a b) (c d))
				tmp)`

	l := lex.NewLexer(input)
	tokens := l.ReadTokens()
	p := parse.NewParser(tokens)
	program, err := p.ParseProgram()
	if err != nil {
		t.Fatalf("ParseProgram error: %v", err)
	}

	env := obj.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	result, err := eval.EvalProgram(program, env)
	if err != nil {
		t.Fatalf("EvalProgram error: %v", err)
	}

	// The outer tmp should remain 999 if the macro temporaries are hygienic
	if result.Inspect() != "999" {
		t.Fatalf("expected outer tmp to be 999, got %s", result.Inspect())
	}
}
